import wx
import wx.lib.customtreectrl as CT
from rsa.loader.rule_set_loader import DynamicRuleSetLoader
from rsa.util import common_utilities
from rsa import rulesets
from rsa.loader.config_loader import ConfigLoader
import os

class RuleEditor(object):
    '''
    This class represent a interface to encapsulate the functionality to
    configure rules 
    '''
    
    def configure_rules(self, rulesets, include):
        raise NotImplementedError("Should have implemented this")

class SimpleRuleEditor(RuleEditor, wx.Frame):

    defaultLocation = "/tmp/RSA/rsa.cfg"
    
    INITIAL_SIZE = [900, 600]
    
    def __init__(self, parent, title):
        super(SimpleRuleEditor, self).__init__(parent, title=title,
            size=(self.INITIAL_SIZE[0], self.INITIAL_SIZE[1]))
        self._init_ui()
        
    def _init_ui(self):
        ''' Initialize main UI panel'''
        # create center panel and add all the required controls
        panel = wx.Panel(self)
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self._init_notebook(panel), 1, wx.ALL | wx.EXPAND, 1)
        panel.SetSizer(sizer)
        self.Layout()
    
    def _init_notebook(self, panel):
        ''' Initialize main notebook and add all the required components '''
        notebook = wx.Notebook(panel, id=wx.ID_ANY, style=wx.BK_DEFAULT)
        # create main panel
        tree_panel = RuleTreeCtrlPanel(notebook)
        tree_panel.SetBackgroundColour('#f5f5f5')
        vbox = wx.BoxSizer(wx.VERTICAL) 
        
        hb1 = wx.BoxSizer(wx.HORIZONTAL)
        self.tip = wx.StaticText(tree_panel, label='Select the rules to be used by the static analyzer:')
        hb1.Add(self.tip, flag=wx.RIGHT, border=8)
        
        # hbox will contain the tree and the info box. info box will be vertically organized
        hbox = wx.BoxSizer(wx.HORIZONTAL)
        self.tree = tree_panel
        hbox.Add(self.tree.tree, 2, wx.EXPAND | wx.ALL, 15)
        
        # vInfoBox to contain all the information controls, etc
        vInfoBox = wx.BoxSizer(wx.VERTICAL)
        self.rule_info = wx.StaticText(tree_panel, label='Rule Information')
        font = self.rule_info.GetFont()
        font.SetWeight(wx.BOLD)
        self.rule_info.SetFont(font) 
        # Text Control to show the rule information
        self.r_info_ctrl = wx.TextCtrl(tree_panel, style=wx.TE_MULTILINE)
        self.r_info_ctrl.SetEditable(False)
        self.tree.set_rule_info_ctrl(self.r_info_ctrl)
        # Text Control to show and edit the rule properties and button to save changes
        self.rule_conf = wx.StaticText(tree_panel, label='Rule Configuration')
        self.rule_conf.SetFont(font)
        self.rule_conf_label = wx.StaticText(tree_panel, label='')
        self.tree.set_conf_label(self.rule_conf_label)
        self.r_conf_ctrl = wx.TextCtrl(tree_panel, style=wx.TE_MULTILINE)
        self.r_conf_ctrl.Disable()
        self.tree.set_rule_conf_ctrl(self.r_conf_ctrl)
        self.save_button = wx.Button(tree_panel, label = "Save Configuration")
        self.Bind(wx.EVT_BUTTON, self.save_rule_conf, self.save_button)
        self.save_button.Disable()
        self.tree.set_save_button(self.save_button)
        # Here all the components are included in the vertical info box
        vInfoBox.Add(self.rule_info, flag=wx.RIGHT, border=8)
        vInfoBox.Add(self.r_info_ctrl, 2, wx.EXPAND | wx.ALL, 15)
        vInfoBox.Add(self.rule_conf, flag=wx.RIGHT, border=8)  
        vInfoBox.Add(self.rule_conf_label, flag=wx.RIGHT, border=8)
        vInfoBox.Add(self.r_conf_ctrl, 1, wx.EXPAND | wx.ALL, border=15)
        vInfoBox.Add(self.save_button, 0, wx.ALIGN_RIGHT | wx.ALL, border=15)
        # The recently created vInfoBox is appended besides the tree in the hbox
        hbox.Add(vInfoBox, 3, wx.EXPAND | wx.ALL, 15)
        
        # add components to main panel
        vbox.Add(hb1, flag=wx.EXPAND | wx.LEFT | wx.RIGHT | wx.TOP, border=5)
        vbox.Add((-1, 5))
        vbox.Add(hbox, proportion=1, flag=wx.LEFT | wx.RIGHT | wx.EXPAND, border=10)
        
        # Box sizer for the save and cancel buttons
        hbutton_box = wx.BoxSizer(wx.HORIZONTAL)
        self.save_all_button = wx.Button(tree_panel, label = "Save")
        self.cancel_button = wx.Button(tree_panel, label = "Cancel")
        hbutton_box.Add(self.save_all_button, 1, wx.ALL, border=8)
        hbutton_box.Add(self.cancel_button, 1, wx.ALL, border=8)
        self.Bind(wx.EVT_BUTTON, self.save_included_rules, self.save_all_button)
        self.Bind(wx.EVT_BUTTON, self.cancel, self.cancel_button)
        # Here the buttons are added in the vbox, below the tree and info row in the hbox
        vbox.Add(hbutton_box, proportion=0, flag=wx.ALIGN_RIGHT | wx.ALL, border=10)
        
        tree_panel.SetSizer(vbox)
        # add main panel to notebook and return component
        notebook.AddPage(tree_panel, "Rules")
        return notebook
    
    def configure_rules(self, rulesets, include=[]):
        '''Initial Method, fills the tree with the whole ruleset and the already included rules'''
        if(rulesets == None): raise Exception('A rulesets array is need.')
        if(self.tree == None): raise Exception('Rulesets tree was no initialized.')
        self.tree.fill_tree(rulesets, include)
        self.Center()
        self.Show()
    
    def save_included_rules(self, event):
        '''When user clicks the "Save" button, this method updates the included rules with the tree selection'''
        included_rules = [] 
        include = self.get_included_rules(self.tree.tree.GetRootItem(), included_rules)
        ConfigLoader().save(include, self.defaultLocation)
        self.Close()
    
    def save_rule_conf(self, event):
        '''When user clicks the 'Save Configuration', this method updates the rule property with the new value'''
        value = self.r_conf_ctrl.GetValue()
        ConfigLoader().save_conf(self.tree.get_current_rule_id(), value, self.defaultLocation)
        self.save_button.Disable()
    
    def get_included_rules(self, itemParent=None, included_rules=[]):
        '''Recursive method, returns an array with all the selected rulesets and rules in the tree'''
        child, cookie = self.tree.tree.GetFirstChild(itemParent)
        while child: 
            if self.tree.tree.IsItemChecked(child):
                if child._parent._data == None: 
                    # This is a checked ruleset, append it to the included ones
                    included_rules.append(self.tree.tree.GetPyData(child).name)
                elif child._parent._data.name not in included_rules:       
                    # This is a checked rule, append it to the included ones if the parent is not included
                    included_rules.append(self.tree.tree.GetPyData(child).rule_id)
            included_rules = self.get_included_rules(child, included_rules) 
            child, cookie = self.tree.tree.GetNextChild(itemParent, cookie) 
        return included_rules
    
    def cancel(self, event):
        '''When user clicks the 'Cancel' button, quit'''
        self.Close()

class MyTreeCtrl(CT.CustomTreeCtrl):
    
    '''The CustomTreeCtrl, which will manage the ruleset tree, it's similar to TreeCtrl but it supports checkboxes'''
    def __init__(self, parent, id, pos, size, style):
        CT.CustomTreeCtrl.__init__(self, parent, id, pos, size, style)  

class RuleTreeCtrlPanel(wx.Panel):
    
    def __init__(self, parent, rulesets=None):
        '''Inits the panel and the CustomTreeCtrl'''
        wx.Panel.__init__(self, parent, -1, style=wx.WANTS_CHARS)
        tID = wx.NewId()
        self.tree = MyTreeCtrl(self, tID, wx.DefaultPosition, wx.DefaultSize, wx.TR_DEFAULT_STYLE)  
        self.tree.Bind(CT.EVT_TREE_ITEM_CHECKING, self.onCheckingItem, self.tree)
        self.tree.Bind(CT.EVT_TREE_SEL_CHANGED, self.onSelectingItem, self.tree)
        if (rulesets):
            self.fill_tree(rulesets)
    
    def onSelectingItem(self, event):
        '''When user clicks a tree label, this shows the rule info or clears the panel if there's nothing to show'''
        item = event._item
        if len(item._children)== 0:
            self.show_rule_info(self.tree.GetPyData(item))
            properties = common_utilities.load_option(self.tree.GetPyData(item).rule_id)
            self.show_rule_properties(properties)
            self.current_rule_id = self.tree.GetPyData(item).rule_id
        else:
            self.rule_info_ctrl.Clear()
            self.rule_conf_label.SetLabel("")
            self.rule_conf_ctrl.Clear()
            self.rule_conf_ctrl.Disable()
        
    def onCheckingItem(self, event):
        '''When user clicks a tree checkbox, this checks/unchecks and shows related info'''      
        item = event._item
        if item._checked:
            # Uncheck the childs
            self.tree.CheckChilds(item, False)
            # Uncheck the item
            self.tree.CheckItem2(item, False)
            # If parent isn't the root, Uncheck the parent 
            if item._parent._parent != None:
                self.tree.CheckItem2(item._parent, False)
            self.rule_info_ctrl.Clear()
            self.rule_conf_label.SetLabel("")
            self.rule_conf_ctrl.Clear()
            self.rule_conf_ctrl.Disable()
        else:
            # Check the childs
            self.tree.CheckChilds(item, True)
            # Check the item
            self.tree.CheckItem2(item, True)
            if len(item._children)== 0: #If user is selecting a rule
                # Show the information rule
                self.show_rule_info(self.tree.GetPyData(item))
                # Load properties
                properties = common_utilities.load_option(self.tree.GetPyData(item).rule_id)
                self.show_rule_properties(properties)
                self.current_rule_id = self.tree.GetPyData(item).rule_id
        self.tree.Refresh()
    
    def fill_tree(self, rulesets=None, include=[]):
        '''This method fills the tree with rulesets and rules, checks the already saved "included" and initially expand some items'''
        if rulesets:
            # Adds the root
            self.root = self.tree.AddRoot("All rule sets")
            self.tree.SetPyData(self.root, None) 
            for ruleset in rulesets: 
                # Adds a ruleset and determines if it's already included, in order to check it
                child = self.tree.AppendItem(self.root, ruleset.name, ct_type=1)
                parent_included = ruleset.name in include 
                if parent_included:
                    self.tree.CheckItem2(child, True)
                self.tree.SetPyData(child, ruleset)
                for rule in ruleset.rules:
                    # Adds a rule and determines if it's already included (or his parent), in order to check it
                    last = self.tree.AppendItem(child, rule.name, ct_type=1)
                    if parent_included or rule.rule_id in include:
                        self.tree.CheckItem2(last, True)
                        if not parent_included:
                            # Expand the parent ruleset to show there's a child checked
                            self.tree.Expand(child)
                    self.tree.SetPyData(last, rule)
            self.tree.Expand(self.root)

    def set_rule_info_ctrl(self, r_info):
        self.rule_info_ctrl = r_info
    
    def set_rule_conf_ctrl(self, r_conf):
        self.rule_conf_ctrl = r_conf
        self.rule_conf_ctrl.Bind(wx.EVT_SET_FOCUS, self.onTextEnter, self.rule_conf_ctrl)
    
    def set_conf_label(self, conf_label):
        self.rule_conf_label = conf_label;
    
    def set_save_button(self, save_but):
        self.save_button = save_but
        
    def get_current_rule_id(self):
        return self.current_rule_id
    
    def onTextEnter(self, event): 
        self.save_button.Enable()
    
    def show_rule_properties(self, properties):
        '''If the rule has any property, this method shows it, else it clears the config control'''
        if properties!= None and len(properties) > 0:
            self.rule_conf_ctrl.Enable()
            self.rule_conf_ctrl.Clear()
            text = ""
            for p in properties:
                text = text + p + ","
            self.rule_conf_ctrl.AppendText(text[:-1])
        else:
            self.rule_conf_ctrl.Clear()
            self.rule_conf_ctrl.Disable()
            self.save_button.Disable()
    
    def show_rule_info(self, rule):
        '''This method shows the info of each rule in the related control'''
        self.rule_info_ctrl.Clear()
        insert = self.rule_info_ctrl.GetInsertionPoint() 
        self.add_rich_info("Name: " )
        self.add_normal_info(rule.name + "\n\n")
        self.add_rich_info("Description: ")
        self.add_normal_info(rule.description + "\n\n")
        self.add_rich_info("Example: ")
        self.add_normal_info(rule.example + "\n")
        severities = ["Error", "Warning", "Suggestion"]
        self.add_rich_info("Severity: " )
        self.add_normal_info(severities[rule.severity]) 
        self.rule_info_ctrl.SetInsertionPoint(insert) 
        
    def add_rich_info(self, text):
        '''Add text to the info control in the defined as rich format'''
        self.set_rich_style()
        self.rule_info_ctrl.AppendText(text)
        
    def add_normal_info(self, text):
        '''Add text to the info control as normal text'''
        self.set_normal_style()
        self.rule_info_ctrl.AppendText(text)    
    
    def set_rich_style(self):
        '''Defines and sets the rich style of the info ctrl'''
        style = self.rule_info_ctrl.GetDefaultStyle()
        font = style.GetFont()
        old_colour = style.GetTextColour()
        style.SetTextColour(wx.RED)
        font.SetWeight(wx.BOLD)
        style.SetFont(font)
        self.rule_info_ctrl.SetDefaultStyle(style)
    
    def set_normal_style(self):
        '''Defines and sets the normal style of the info ctrl'''
        style = self.rule_info_ctrl.GetDefaultStyle()
        font = style.GetFont()
        style.SetTextColour(wx.BLACK)
        font.SetWeight(wx.NORMAL)
        style.SetFont(font)
        self.rule_info_ctrl.SetDefaultStyle(style)
        
